package com.sanji.audio_test

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Environment
import android.util.Log
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.preference.PreferenceManager
import com.sanji.audio_test.format.Format
import com.sanji.audio_test.template.Template
import com.sanji.audio_test.output.Retention
import java.io.File
import kotlinx.serialization.json.Json

/**
 * 应用程序偏好设置管理类
 * 负责管理应用的所有配置选项和用户设置
 */
class Preferences(initialContext: Context) {
    companion object {
        private val TAG = Preferences::class.java.simpleName

        // 偏好设置键名常量
        const val PREF_ENABLED = "enabled"           // 启用状态键名
        const val PREF_OPENING_STATEMENT = "opening_statement"     // 开场白键名
        const val PREF_OUTPUT_DIR = "output_dir"
        const val PREF_FILENAME_TEMPLATE = "filename_template"
        const val PREF_OUTPUT_RETENTION = "output_retention"
        const val PREF_RECORD_RULES = "record_rules"  // 添加录音规则键名
        const val PREF_CALL_RECORDING = "call_recording"  // 启用通话录制
        const val PREF_MIN_DURATION = "min_duration"  // 最小录制时长
        const val PREF_RECORD_TELECOM_APPS = "record_telecom_apps"  // 录制telecom应用通话
        const val PREF_RECORD_DIALING_STATE = "record_dialing_state"  // 录制拨号状态
        const val PREF_ADD_NEW_RULE = "add_new_rule"  // 添加新规则
        private const val PREF_WRITE_METADATA = "write_metadata"  // 写入元数据
        private const val PREF_FORCE_DIRECT_BOOT = "force_direct_boot"  // 强制直接启动
        private const val PREF_DEBUG_MODE = "debug_mode"  // 调试模式
        
        // 不与UI偏好设置关联的键名
        private const val PREF_FORMAT_NAME = "format_name"
        private const val PREF_FORMAT_PARAM_PREFIX = "format_param_"
        private const val PREF_FORMAT_SAMPLE_RATE_PREFIX = "format_sample_rate_"
        private const val PREF_NEXT_NOTIFICATION_ID = "next_notification_id"  // 通知ID
        private const val PREF_ALREADY_MIGRATED = "already_migrated"  // 已迁移标志

        // 默认值
        val DEFAULT_FILENAME_TEMPLATE = Template(
            "{date}" +
                    "[_{direction}|]" +
                    "[_sim{sim_slot}|]" +
                    "[_{phone_number}|]" +
                    "[_[{contact_name}|{caller_name}|{call_log_name}]|]"
        )
        
        // 默认录音规则
        val DEFAULT_RECORD_RULES = listOf(
            RecordRule(
                callNumber = RecordRule.CallNumber.Any,
                callType = RecordRule.CallType.ANY,
                simSlot = RecordRule.SimSlot.Any,
                action = RecordRule.Action.SAVE,
            ),
        )
        
        // JSON格式化配置，忽略未知键
        private val JSON_FORMAT = Json { ignoreUnknownKeys = true }

        /**
         * 判断键名是否为格式相关设置
         * @param key 设置键名
         * @return 如果是格式相关设置返回true，否则返回false
         */
        fun isFormatKey(key: String): Boolean =
            key == PREF_FORMAT_NAME
                    || key.startsWith(PREF_FORMAT_PARAM_PREFIX)
                    || key.startsWith(PREF_FORMAT_SAMPLE_RATE_PREFIX)
    }

    // 应用上下文
    private val context = initialContext
    // 共享偏好设置
    private val prefs = PreferenceManager.getDefaultSharedPreferences(context)

    /**
     * 是否启用通话音频播放功能
     * 
     * getter: 获取当前启用状态，默认为false（未启用）
     * setter: 设置启用状态并保存到偏好设置中
     */
    var isEnabled: Boolean
        get() = prefs.getBoolean(PREF_ENABLED, false)
        set(enabled) = prefs.edit { putBoolean(PREF_ENABLED, enabled) }

    /**
     * 音频文件URI
     * 
     * getter: 从偏好设置中获取存储的音频文件URI，如果不存在则返回null
     * setter: 设置新的音频文件URI，并处理URI权限的获取和释放
     */
    var openingStatement: Uri?
        get() = prefs.getString(PREF_OPENING_STATEMENT, null)?.let { Uri.parse(it) }
        set(uri) {
            val oldUri = openingStatement
            // 如果新旧URI相同或都为null，则无需更新
            if (oldUri == uri) {
                // URI 与之前相同或者都为 null
                return
            }

            prefs.edit {
                if (uri != null) {
                    try {
                        // 首先为新的 URI 获取持久化权限
                        context.contentResolver.takePersistableUriPermission(
                            uri,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION
                        )
                        // 保存新的 URI 字符串到偏好设置
                        putString(PREF_OPENING_STATEMENT, uri.toString())
                    } catch (e: SecurityException) {
                        // 如果无法获取持久化权限，仍然保存 URI，但不获取权限
                        // 这种情况常见于媒体库 URI
                        putString(PREF_OPENING_STATEMENT, uri.toString())
                        // 可以记录日志或进行其他处理
                    }
                } else {
                    // 如果 URI 为 null，则从偏好设置中移除该键
                    remove(PREF_OPENING_STATEMENT)
                }
            }

            // 只有在新 URI 设置成功后才释放旧 URI 的持久化权限，
            // 以确保操作的原子性，避免在权限转移过程中出现无权限访问的情况
            if (oldUri != null) {
                try {
                    context.contentResolver.releasePersistableUriPermission(
                        oldUri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION
                    )
                } catch (e: SecurityException) {
                    // 忽略权限释放失败的情况
                }
            }
        }
    
    /**
     * 获取无符号整数偏好设置值
     *
     * @param key 偏好设置键名
     * @param sentinel 哨兵值，用于标识未设置的情况
     * @return 偏好设置值，永远不会是[sentinel]
     */
    private fun getOptionalUint(key: String, sentinel: UInt): UInt? {
        // 使用哨兵值是因为contains + getInt会导致TOCTOU问题
        val value = prefs.getInt(key, sentinel.toInt())

        return if (value == sentinel.toInt()) {
            null
        } else {
            value.toUInt()
        }
    }

    /**
     * 设置无符号整数偏好设置值
     *
     * @param key 偏好设置键名
     * @param sentinel 哨兵值
     * @param value 要设置的值，不能为[sentinel]
     *
     * @throws IllegalArgumentException 如果[value]等于[sentinel]
     */
    private fun setOptionalUint(key: String, sentinel: UInt, value: UInt?) {
        if (value == sentinel) {
            throw IllegalArgumentException("$key 值不能为 $sentinel")
        }

        prefs.edit {
            if (value == null) {
                remove(key)
            } else {
                putInt(key, value.toInt())
            }
        }
    }

        /** 是否显示调试偏好设置并为所有通话启用调试日志创建 */
    var isDebugMode: Boolean
        get() = BuildConfig.FORCE_DEBUG_MODE || prefs.getBoolean(PREF_DEBUG_MODE, false)
        set(enabled) = prefs.edit { putBoolean(PREF_DEBUG_MODE, enabled) }

    
    /**
     * 保存的输出格式。
     *
     * 使用[getFormatParam]/[setFormatParam]获取/设置格式特定参数。
     * 使用[getFormatSampleRate]/[setFormatSampleRate]获取/设置格式特定采样率。
     */
    var format: Format?
        get() = prefs.getString(PREF_FORMAT_NAME, null)?.let { Format.getByName(it) }
        set(format) = prefs.edit {
            if (format == null) {
                remove(PREF_FORMAT_NAME)
            } else {
                putString(PREF_FORMAT_NAME, format.name)
            }
        }

    /**
     * 获取[format]的格式特定参数。
     * @param format 格式对象
     * @return 格式参数
     */
    fun getFormatParam(format: Format): UInt? =
        getOptionalUint(PREF_FORMAT_PARAM_PREFIX + format.name, UInt.MAX_VALUE)

    /**
     * 设置[format]的格式特定参数。
     *
     * @param format 格式对象
     * @param param 参数值，不能为[UInt.MAX_VALUE]
     *
     * @throws IllegalArgumentException 如果[param]为[UInt.MAX_VALUE]
     */
    fun setFormatParam(format: Format, param: UInt?) =
        setOptionalUint(PREF_FORMAT_PARAM_PREFIX + format.name, UInt.MAX_VALUE, param)

    /**
     * 获取[format]的格式特定采样率。
     * @param format 格式对象
     * @return 采样率
     */
    fun getFormatSampleRate(format: Format): UInt? =
        getOptionalUint(PREF_FORMAT_SAMPLE_RATE_PREFIX + format.name, 0U)

    /**
     * 设置[format]的格式特定采样率。
     *
     * @param format 格式对象
     * @param rate 采样率，不能为0
     *
     * @throws IllegalArgumentException 如果[rate]为0
     */
    fun setFormatSampleRate(format: Format, rate: UInt?) =
        setOptionalUint(PREF_FORMAT_SAMPLE_RATE_PREFIX + format.name, 0U, rate)
        
    /**
     * 获取默认输出目录。该目录应该始终可写，适合作为备用目录。
     */
    val defaultOutputDir: File
        get() = context.getExternalFilesDir(null)!!
        
    /**
     * 用户指定的输出目录。
     *
     * URI（如果不为空）指向SAF提供的写入持久化URI。当设置新URI时，
     * 旧URI的持久化URI权限将被撤销，并请求新URI的持久化写入权限。
     * 如果新旧URI相同，则不执行任何操作。如果为新URI持久化权限失败，则不更改保存的输出目录。
     */
    var outputDir: Uri?
        get() = prefs.getString(PREF_OUTPUT_DIR, null)?.toUri()
        set(uri) {
            val oldUri = outputDir
            if (oldUri == uri) {
                // URI与之前相同或都为空
                return
            }

            prefs.edit {
                if (uri != null) {
                    // 首先持久化新URI的权限
                    try {
                        context.contentResolver.takePersistableUriPermission(
                            uri,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                        )
                        putString(PREF_OUTPUT_DIR, uri.toString())
                    } catch (e: SecurityException) {
                        Log.w(TAG, "无法获取持久化URI权限: $uri", e)
                    }
                } else {
                    remove(PREF_OUTPUT_DIR)
                }
            }

            // 只有在设置新URI后才释放旧目录的持久化权限以保证原子性
            if (oldUri != null) {
                try {
                    context.contentResolver.releasePersistableUriPermission(
                        oldUri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    )
                } catch (e: Exception) {
                    Log.w(TAG, "释放持久化URI权限时出错: $oldUri", e)
                }
            }
        }
        
    /**
     * 获取用户指定的输出目录或默认目录（如果未设置）。此方法不执行任何文件系统操作来检查用户指定的目录是否仍然有效。
     */
    val outputDirOrDefault: Uri
        get() = outputDir ?: Uri.fromFile(defaultOutputDir)
        
    /**
     * 用户指定的文件名模板
     */
    var filenameTemplate: Template?
        get() = prefs.getString(PREF_FILENAME_TEMPLATE, null)?.let { Template(it) }
        set(template) = prefs.edit {
            if (template == null) {
                remove(PREF_FILENAME_TEMPLATE)
            } else {
                putString(PREF_FILENAME_TEMPLATE, template.toString())
            }
        }
        
    /**
     * 保存的文件保留时间（天数）。
     *
     * 不能为[UInt.MAX_VALUE]。
     */
    var outputRetention: Retention?
        get() = getOptionalUint(PREF_OUTPUT_RETENTION, UInt.MAX_VALUE)?.let {
            Retention.fromRawPreferenceValue(it)
        }
        set(retention) = setOptionalUint(PREF_OUTPUT_RETENTION, UInt.MAX_VALUE,
            retention?.toRawPreferenceValue())
            
    /** 用于确定对特定通话采取何种操作的规则列表 */
    var recordRules: List<RecordRule>?
        get() = prefs.getString(PREF_RECORD_RULES, null)?.let { JSON_FORMAT.decodeFromString(it) }
        set(rules) = prefs.edit {
            if (rules == null) {
                remove(PREF_RECORD_RULES)
            } else {
                putString(PREF_RECORD_RULES, JSON_FORMAT.encodeToString(rules))
            }
        }
        
    /**
     * 是否启用通话录制。
     */
    var isCallRecordingEnabled: Boolean
        get() = prefs.getBoolean(PREF_CALL_RECORDING, false)
        set(enabled) = prefs.edit { putBoolean(PREF_CALL_RECORDING, enabled) }

    /**
     * 最小录制时长，只有在录制规则允许保存的情况下才会保留。
     */
    var minDuration: Int
        get() = prefs.getInt(PREF_MIN_DURATION, 0)
        set(seconds) = prefs.edit { putInt(PREF_MIN_DURATION, seconds) }

    /**
     * 是否录制来自telecom集成应用的通话。
     */
    var recordTelecomApps: Boolean
        get() = prefs.getBoolean(PREF_RECORD_TELECOM_APPS, false)
        set(enabled) = prefs.edit { putBoolean(PREF_RECORD_TELECOM_APPS, enabled) }

    /**
     * 是否在通话进入拨号状态时立即开始录制。
     */
    var recordDialingState: Boolean
        get() = prefs.getBoolean(PREF_RECORD_DIALING_STATE, false)
        set(enabled) = prefs.edit { putBoolean(PREF_RECORD_DIALING_STATE, enabled) }

    /**
     * 是否写入通话元数据文件。
     */
    var writeMetadata: Boolean
        get() = prefs.getBoolean(PREF_WRITE_METADATA, false)
        set(enabled) = prefs.edit { putBoolean(PREF_WRITE_METADATA, enabled) }

    /**
     * 获取每次调用时递增的唯一通知ID。
     */
    val nextNotificationId: Int
        get() = synchronized(context.applicationContext) {
            val nextId = prefs.getInt(PREF_NEXT_NOTIFICATION_ID, 0)
            prefs.edit { putInt(PREF_NEXT_NOTIFICATION_ID, nextId + 1) }
            nextId
        }
        
    /**
     * 移除默认格式偏好设置和所有格式的参数。
     */
    fun resetAllFormats() {
        val keys = prefs.all.keys.filter(::isFormatKey)
        prefs.edit {
            for (key in keys) {
                remove(key)
            }
        }
    }
}